package org.finesys.tika.parser;

import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.finesys.chat.core.base.document.Document;
import org.finesys.chat.core.base.document.exception.BlankDocumentException;
import org.finesys.chat.core.base.document.parser.DocumentParser;
import org.finesys.common.core.util.Utils;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * apacheTika 文档解析器
 */
public class ApacheTikaDocumentParser implements DocumentParser {

    private static final int NO_WRITE_LIMIT = -1;

    private static final Supplier<Parser> DEFAULT_PARSER_SUPPLIER = AutoDetectParser::new;
    private static final Supplier<Metadata> DEFAULT_METADATA_SUPPLIER = Metadata::new;
    private static final Supplier<ParseContext> DEFAULT_PARSE_CONTEXT_SUPPLIER = ParseContext::new;
    private static final Supplier<ContentHandler> DEFAULT_CONTENT_HANDLER_SUPPLIER = () -> {
        return new BodyContentHandler(NO_WRITE_LIMIT);
    };

    private final Supplier<Parser> parserSupplier;
    private final Supplier<ParseContext> parseContextSupplier;
    private final Supplier<ContentHandler> contentHandlerSupplier;
    private final Supplier<Metadata> metadataSupplier;
    private final boolean includeMetadata;

    public ApacheTikaDocumentParser() {
        this(false);
    }

    public ApacheTikaDocumentParser(boolean includeMetadata) {
        this(null, null, null, null, includeMetadata);
    }

    public ApacheTikaDocumentParser(Supplier<Metadata> metadataSupplier, Supplier<Parser> parserSupplier, Supplier<ParseContext> parseContextSupplier, Supplier<ContentHandler> contentHandlerSupplier, boolean includeMetadata) {
        this.metadataSupplier = Utils.getOrDefault(metadataSupplier, DEFAULT_METADATA_SUPPLIER);
        this.parseContextSupplier = Utils.getOrDefault(parseContextSupplier, DEFAULT_PARSE_CONTEXT_SUPPLIER);
        this.contentHandlerSupplier = Utils.getOrDefault(contentHandlerSupplier, DEFAULT_CONTENT_HANDLER_SUPPLIER);
        this.parserSupplier = Utils.getOrDefault(parserSupplier, DEFAULT_PARSER_SUPPLIER);
        this.includeMetadata = includeMetadata;
    }

    @Override
    public Document parse(InputStream inputStream) {
        try {
            Parser parser = this.parserSupplier.get();
            Metadata metadata = this.metadataSupplier.get();
            ParseContext parseContext = this.parseContextSupplier.get();
            ContentHandler contentHandler = this.contentHandlerSupplier.get();
            parser.parse(inputStream, contentHandler, metadata, parseContext);
            String text = contentHandler.toString();
            if (Utils.isNullOrBlank(text)) {
                throw new BlankDocumentException("文档内容为空");
            } else {
                return includeMetadata ? Document.from(text, this.convert(metadata)) : Document.from(text);
            }
        } catch (BlankDocumentException e) {
            throw e;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (TikaException e) {
            throw new RuntimeException(e);
        }

    }

    private org.finesys.chat.core.base.document.Metadata convert(Metadata metadata) {
        Map<String, Object> map = new HashMap<>();
        String[] tikaNames = metadata.names();
        for (String tikaName : tikaNames) {
            map.put(tikaName, String.join(";", metadata.getValues(tikaName)));
        }

        return org.finesys.chat.core.base.document.Metadata.from(map);
    }
}
